import common from '../../dxmodules/dxCommon.js';
import log from '../../dxmodules/dxLogger.js'
import config from '../../dxmodules/dxConfig.js'
import base64 from '../../dxmodules/dxBase64.js'
import dxMap from '../../dxmodules/dxMap.js'
import ota from "../../dxmodules/dxOta.js";
import driver from '../driver.js';
import utils from '../common/utils/utils.js';
import configService from '../service/configService.js'
import configConst from '../common/consts/configConst.js';

const codeService = {}

codeService.receiveMsg = function (data) {
    // log.info('[codeService] receiveMsg :' + JSON.stringify(data))
    let str = common.utf8HexToStr(common.arrayBufferToHexString(data.data))
    this.code({ source: 'code', code: str, type: data.type })
}

//Password reporting and QR code scanning are done in one process
codeService.code = function (data) {
    // log.info('[codeService]  code:' + JSON.stringify(data))
    let code = data.code
    const de_type = config.get('sysInfo.de_type');
    // 1. Handle always allowed code types (configuration codes)
    if (code.startsWith("__VGS__0") || code.startsWith("___VBAR_CONFIG_V1.1.0___")) {
        const configType = code.startsWith("__VGS__0") ? "new" : "old";
        configCode(code, configType);
        return;
    }
    // 2. Processing code types that require verification.
    // In protocol mode, QR/configuration code is always enabled. In pass-through mode, configuration code is always enabled, QR code is configured, and password is not blocked.
    if (shouldProcessCode(de_type, data.type) || data.source == "password") {
        formatCode(code, data.source)
    }
}

function shouldProcessCode (de_type, type) {
    // Special handling: Processing is allowed even if all or QR codes are disabled (for configuration codes, but configuration codes are handled separately).
    if (type == 1) { // QR code
        return (de_type & 1) !== 0;
    } else { // Other coding systems
        const bitPosition = type - 1;
        return (de_type & (1 << bitPosition)) !== 0;
    }
}

//The code data output processing uses the same logic for both keyboard and barcode scanning, distinguished by the source.
function formatCode(msg, source) {
    if (config.get('sysInfo.codeSwitch') == 0 && source == 'code') {
        return
    }
    // Processing encoding formats
    let encodedMsg = msg;
    // Pass-through marker
    let flag = false
    switch (config.get('sysInfo.ft')) {
        case 1:
            // char (numeric character) to hex
            encodedMsg = /^\d+(\.\d+)?$/.test(msg) ? utils.decimalToLittleEndian(parseInt(msg)) : '00'
            break;
        case 2:
            // char (numeric character) to hex (big endian)
            encodedMsg = /^\d+(\.\d+)?$/.test(msg) ? utils.decimalToLittleEndian(parseInt(msg)).match(/.{1,2}/g).reverse().join('') : '00'
            break;
        case 4:
            if (!isHexadecimal(msg) && msg.length % 2 === 0) {
                return
            }
            encodedMsg = msg
            break;
        case 8:
            if (!isHexadecimal(msg) && msg.length % 2 != 0) {
                return
            }
            // Convert char (hexadecimal character) to hexadecimal and reverse its order.
            encodedMsg = msg.match(/.{1,2}/g).reverse().join('')
            break;
        default:
            // Original message unchanged
            encodedMsg = common.strToUtf8Hex(msg)
            flag = true
            break
    }
    let prefix = config.get('sysInfo.prefix')
    let postfix = config.get('sysInfo.postfix')
    //Add prefixes and suffixes
    if (config.get('sysInfo.chorc') == 2) {
        //Convert prefixes and suffixes to hexadecimal.
        if (isHexadecimal(prefix) && prefix.length % 2 === 0) {
            encodedMsg = (/\s/.test(prefix) ? '' : prefix) + encodedMsg
        }
        if (isHexadecimal(postfix) && postfix.length % 2 === 0) {

            encodedMsg += (/\s/.test(postfix) ? '' : postfix)
        }
    } else if (config.get('sysInfo.chorc') == 1) {
        //character form
        encodedMsg = common.stringToHex(prefix) + encodedMsg
        encodedMsg += common.stringToHex(postfix)
    }
    //Add a carriage return space
    // if (config.get('sysInfo.nl') == 1 && !flag) {
    if (config.get('sysInfo.nl') == 1) {
        //Add a carriage return
        encodedMsg += "0d";
    }
    if (config.get('sysInfo.cr') == 1) {
        //Add line breaks
        encodedMsg += "0a";
    }

    if ((config.get('sysInfo.ascan') & 1) == 1) {
        driver.pwm.beep(config.get('sysInfo.beepd'), null, 1)
    }
    // log.info('The final scanned/password output data is', encodedMsg);

    driver.passage.beforeReport({ source: source, data: encodedMsg })

}
// Configuration code processing
function configCode(code, type) {
    if (!checkConfigCode(code, type)) {
        driver.pwm.fail()
        log.error("Configuration code verification failed.")
        return
    }
    let json = utils.parseString(code)
    if (Object.keys(json).length <= 0) {
        try {
            json = JSON.parse(code.slice(code.indexOf("{"), code.lastIndexOf("}") + 1))
        } catch (error) {
            log.error(error)
        }
    }
    log.info("Parse configuration code: ", JSON.stringify(json))

    //Switch mode
    if (!utils.isEmpty(json.w_model)) {
        try {
            common.setMode(json.w_model)
            driver.pwm.success()
            common.asyncReboot(1)
        } catch (error) {
            log.error(error)
            log.info('No action is taken if the switch fails.');
            driver.pwm.fail()
        }
        return
    }
    let map = dxMap.get("UPDATE")
    // QR code upgrade related
    if (json.update_flag === 1) {
        if (!driver.net.getStatus()) {
            updateFailed("Please check the network")
            driver.pwm.fail()
            return
        }
        if (map.get("updateFlag")) {
            return
        }

        map.put("updateFlag", true)
        driver.pwm.warning()
        try {
            updateBegin()
            ota.updateHttp(json.update_addr, json.update_md5, 60)
            updateEnd()
            driver.pwm.success()
            common.asyncReboot(1)
        } catch (error) {
            updateFailed(error.message)
            driver.pwm.fail()
        }
        map.del("updateFlag")
        return

    } else if ([2, 3].includes(json.update_flag)) {
        if (utils.isEmpty(json.update_name) || utils.isEmpty(json.update_path)) {
            driver.pwm.fail()
            return
        }
        let downloadPath = "/app/data/upgrades/" + json.update_name
        if (json.update_flag === 2) {
            // Download images, SO, etc.
            return resourceDownload(json.update_addr, json.update_md5, downloadPath, () => {
                common.systemBrief(`mv "${downloadPath}" "${json.update_path}"`)
            })
        } else if (json.update_flag === 3) {
            // Download the compressed file
            return resourceDownload(json.update_addr, json.update_md5, downloadPath, () => {
                common.systemBrief(`unzip -o "${downloadPath}" -d "${json.update_path}"`)
            })
        }
    }
    if (!utils.isEmpty(json.update_flg)) {
        if (map.get("updateFlag")) {
            return
        }
        if (!driver.net.getStatus()) {
            updateFailed("Please check the network")
            driver.pwm.fail()
            return
        }
        map.put("updateFlag", true)
        // Compatible with older upgrade formats
        if (utils.isEmpty(json.update_haddr) || utils.isEmpty(json.update_md5)) {
            driver.pwm.fail()
            map.del("updateFlag")
            return
        }
        try {
            driver.pwm.warning()
            updateBegin()
            ota.updateResource(json.update_haddr, json.update_md5, utils.getUrlFileSize(json.update_haddr) / 1024, codeService.updateResourceShell)
            updateEnd()
            common.asyncReboot(3)
        } catch (error) {
            updateFailed(error.message)
            driver.pwm.fail()
        }
        map.del("updateFlag")
        return
    }
    // Equipment configuration related
    let configData = {}
    for (let key in json) {
        let transKey
        if (type == "new") {
            transKey = key.indexOf(".") >= 0 ? key.split(".")[0] + "Info." + key.split(".")[1] : configConst.getValueByKey(key)
        } else {
            transKey = key.indexOf(".") >= 0 ? key : configConst.getValueByKey(key)
        }
        if (transKey) {
            let keys = transKey.split(".")
            if (utils.isEmpty(configData[keys[0]])) {
                configData[keys[0]] = {}
            }
            configData[keys[0]][keys[1]] = json[key]
        }
    }
    let res = false
    if (Object.keys(json).length > 0) {
        console.log("configData", JSON.stringify(configData));

        res = configService.configVerifyAndSave(configData)
    }
    log.info("Configuration complete (res):" + res)
    if (typeof res != 'boolean') {
        log.error(res)
        driver.pwm.fail()
        return
    }
    if (res) {
        driver.pwm.success()
        log.info("Configuration successful")
    } else {
        driver.pwm.fail()
        log.error("Configuration failed")
    }
    if (json.reboot === 1) {
        driver.screen.customPopWin(config.get("sysInfo.language") == 1 ? "Rebooting" : "Rebooting", 3000, true, "warning")
        common.asyncReboot(1)
    }
}

function updateBegin() {
    if (config.get("sysInfo.language") == 1) {
        driver.screen.customPopWin("Start Upgrading", 600000, true, "waring", false)
    } else {
        driver.screen.customPopWin("Start Upgrading", 600000, true, "waring", false)
    }
}

function updateEnd() {
    if (config.get("sysInfo.language") == 1) {
        driver.screen.customPopWin("Upgrade Successfully", 2000, true, "waring", false)
    } else {
        driver.screen.customPopWin("Upgrade Successfully", 2000, true, "waring", false)
    }
}

codeService.errorMsgMap = {
    "The 'url' and 'md5' param should not be null": "“url” and “md5” cannot be empty",
    "The 'size' param should be a number": "“size” must be a number",
    "The upgrade package is too large, and not be enough space on the disk to download it": "The upgrade package is too large, and not be enough space on the disk to download it",
    "Download failed, please check the url:": "Download failed, please check the url:",
    "Download failed with wrong md5 value": "Download failed with wrong md5 value",
    "Build shell file failed": "Build shell file failed",
    "Upgrade package download failed": "Upgrade package download failed",
    "Please check the network": "Please check the network"
}

function updateFailed(errorMsg) {
    if (errorMsg.includes("Download failed, please check the url")) {
        errorMsg = 'Upgrade package download failed'
    }
    if (config.get("sysInfo.language") == 1) {
        driver.screen.customPopWin("Upgrade Failed: " + (errorMsg ? errorMsg : "Upgrade package download failed"), 600000, true, "waring", false)
    } else {
        driver.screen.customPopWin("Upgrade Failed: " + (codeService.errorMsgMap[errorMsg] ? codeService.errorMsgMap[errorMsg] : "Download failed, please check the website."), 600000, true, "waring", false)
    }
}

// Download general method
function resourceDownload(url, md5, path, cb) {
    // Machine upgrade
    if (utils.isEmpty(url) || utils.isEmpty(md5)) {
        driver.pwm.fail()
        return false
    }
    driver.pwm.warning()
    let ret = utils.waitDownload(url, path, 60 * 1000, md5, utils.getUrlFileSize(url))
    if (!ret) {
        driver.pwm.fail()
        return false
    } else {
        driver.pwm.success()
    }
    if (cb) {
        cb()
    }
    // Download complete, will restart in 1 second.
    common.asyncReboot(1)
    return true
}
codeService.resourceDownload = resourceDownload

//Verify configuration code
function checkConfigCode(code, type) {
    let password = config.get('sysInfo.com_passwd') || '1234567887654321'
    let firstPart
    let secondPart
    if (type == "new") {
        let lastIndex = code.lastIndexOf("__");
        firstPart = code.substring(0, lastIndex);
        secondPart = code.substring(lastIndex + 2);
    } else {
        let lastIndex = code.lastIndexOf("--");
        firstPart = code.substring(0, lastIndex);
        secondPart = code.substring(lastIndex + 2);
    }
    let res
    try {
        res = base64.fromHexString(common.arrayBufferToHexString(common.hmac(firstPart, password)))
    } catch (error) {
        log.error(error)
        return false
    }

    return res == secondPart;
}

// Compatible with older upgrade formats
codeService.updateResourceShell = `
        source=${ota.OTA_ROOT + '/temp'}/vgapp/res/image/bk.png
        target=/app/code/resource/image/background.png
        if test -e "\\$source"; then
            cp "\\$source" "\\$target"
        fi
        source=${ota.OTA_ROOT + '/temp'}/vgapp/res/image/bk_90.png
        target=/app/code/resource/image/background_90.png
        if test -e "\\$source"; then
            cp "\\$source" "\\$target"
        fi
        source=${ota.OTA_ROOT + '/temp'}/vgapp/res/font/PangMenZhengDaoBiaoTiTi-1.ttf
        target=/app/code/resource/font.ttf
        if test -e "\\$source"; then
            cp "\\$source" "\\$target"
        fi
        source=${ota.OTA_ROOT + '/temp'}/vgapp/wav/0.wav
        target=/app/code/resource/wav/0.wav
        if test -e "\\$source"; then
            cp "\\$source" "\\$target"
        fi
        source=${ota.OTA_ROOT + '/temp'}/vgapp/wav/1.wav
        target=/app/code/resource/wav/1.wav
        if test -e "\\$source"; then
            cp "\\$source" "\\$target"
        fi
        source=${ota.OTA_ROOT + '/temp'}/vgapp/wav/2.wav
        target=/app/code/resource/wav/2.wav
        if test -e "\\$source"; then
            cp "\\$source" "\\$target"
        fi
        source=${ota.OTA_ROOT + '/temp'}/vgapp/wav/3.wav
        target=/app/code/resource/wav/3.wav
        if test -e "\\$source"; then
            cp "\\$source" "\\$target"
        fi
        source=${ota.OTA_ROOT + '/temp'}/vgapp/wav/4.wav
        target=/app/code/resource/wav/4.wav
        if test -e "\\$source"; then
            cp "\\$source" "\\$target"
        fi
        source=${ota.OTA_ROOT + '/temp'}/vgapp/wav/5.wav
        target=/app/code/resource/wav/5.wav
        if test -e "\\$source"; then
            cp "\\$source" "\\$target"
        fi
        source=${ota.OTA_ROOT + '/temp'}/vgapp/res/image/*
        target=/app/code/resource/image/
        cp "\\$source" "\\$target"
        source=${ota.OTA_ROOT + '/temp'}/vgapp/wav/*
        target=/app/code/resource/wav/
        cp "\\$source" "\\$target"
        `
//rm -rf ${ota.OTA_ROOT}
//Check if it is a hexadecimal string; otherwise, skip.
function isHexadecimal(str) {
    // Use regular expressions to match the format of a hexadecimal string, specifically strings consisting of 0-9, a-f, and A-F.
    const hexRegex = /^[0-9a-fA-F]+$/;
    return hexRegex.test(str);
}

// Compare whether the first N characters of two strings are equal.
function comparePrefix(str1, str2, N) {
    let substring1 = str1.substring(0, N);
    let substring2 = str2.substring(0, N);
    return substring1 === substring2;
}
export default codeService
